package com.mimo.admin.shiro.config;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.servlet.Filter;

import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.ShiroHttpSession;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;

import com.mimo.admin.config.AdminServerConfig;
import com.mimo.admin.shiro.filter.CustomAccessControlFilter;
import com.mimo.admin.shiro.filter.CustomPermissionsAuthorizationFilter;
import com.mimo.admin.shiro.filter.SessionTimeoutFilter;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;

@Configuration
@EnableConfigurationProperties(value = {AdminServerConfig.class})
public class ShiroConfig {

  private static final String SESSION_ID_NAME = "MIMO_" + ShiroHttpSession.DEFAULT_SESSION_ID_NAME;

  @Autowired
  private AdminServerConfig serverConfig;

  @Value("#{'${mimo.admin.urlPermission}'.split(',')}")
  private List<String> urlPermission;

  @Value("#{'${mimo.admin.routerPermission}'.split(',')}")
  private List<String> routerPermission;

  @Bean
  public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

    // 注入 securityManager
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    shiroFilterFactoryBean.setLoginUrl("/login");
    shiroFilterFactoryBean.setSuccessUrl("/main");
    shiroFilterFactoryBean.setUnauthorizedUrl("/403");

    /**
     * 过滤链， 必须有序 anon 不需要认证 authc 需要认证 user 验证通过或RememberMe登录的都可以
     */
    Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    // 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
    filterChainDefinitionMap.put("/", "anon");
    filterChainDefinitionMap.put("/login", "anon");
    filterChainDefinitionMap.put("/logout", "logout");
    filterChainDefinitionMap.put("/devtools", "anon");
    filterChainDefinitionMap.put("/favicon.ico", "customAccessControl,anon");
    filterChainDefinitionMap.put("/css/**", "customAccessControl,anon");
    filterChainDefinitionMap.put("/images/**", "customAccessControl,anon");
    filterChainDefinitionMap.put("/js/**", "customAccessControl,anon");
    filterChainDefinitionMap.put("/plugins/**", "customAccessControl,anon");
    // <!-- 过滤链定义，从上向下顺序执行，一般将 /**放在最为下边
    // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
    // 自定义加载权限资源关系

    urlPermission.forEach(url -> {
      if (StringUtils.hasText(url)) {
        String permission = "perms[" + url.trim() + "]";
        filterChainDefinitionMap.put(url.trim(), "customAccessControl,authc," + permission);
      }
    });

    routerPermission.forEach(url -> {
      if (StringUtils.hasText(url)) {
        String permission = "perms[" + url.trim() + "]";
        filterChainDefinitionMap.put(url.trim(), "customAccessControl,authc," + permission);
      }
    });

    filterChainDefinitionMap.put("/**", "customAccessControl,authc");

    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

    Map<String, Filter> filters = new LinkedHashMap<>();

    filters.put("customAccessControl",
        new CustomAccessControlFilter(serverConfig.getDomains(), serverConfig.isCsrfEnable()));

    filters.put("authc", new SessionTimeoutFilter());
    filters.put("perms", new CustomPermissionsAuthorizationFilter());

    shiroFilterFactoryBean.setFilters(filters);
    return shiroFilterFactoryBean;
  }

  /**
   * ShiroDialect，为了在thymeleaf里使用shiro的标签的bean
   *
   * @return
   */
  @Bean
  public ShiroDialect shiroDialect() {
    return new ShiroDialect();
  }

  @Bean(name = "securityManager")
  public DefaultWebSecurityManager defaultWebSecurityManager(DefaultWebSessionManager sessionManager) {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

    // <!-- 用户授权/认证信息Cache, 采用EhCache 缓存 -->
    securityManager.setCacheManager(ehCacheManager());
    securityManager.setRealm(shiroRealm());
    securityManager.setSessionManager(sessionManager);
    return securityManager;
  }

  @Bean(name = "sessionManager")
  public DefaultWebSessionManager defaultWebSessionManager() {
    DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();

    sessionManager.setGlobalSessionTimeout(TimeUnit.SECONDS.toMillis(serverConfig.getSessionTimeout()));
    sessionManager.setSessionValidationSchedulerEnabled(true);
    sessionManager.setDeleteInvalidSessions(true);

    sessionManager.setSessionDAO(sessionDao());

    Cookie cookie = new SimpleCookie(SESSION_ID_NAME);
    cookie.setHttpOnly(true);
    sessionManager.setSessionIdCookie(cookie);
    return sessionManager;
  }

  @Bean
  public ShiroRealm shiroRealm() {
    ShiroRealm realm = new ShiroRealm();
    realm.setCachingEnabled(true);
    realm.setCacheManager(ehCacheManager());
    realm.setCredentialsMatcher(credentialsMatcher());
    return realm;
  }

  @Bean
  public CredentialsMatcher credentialsMatcher() {
    return new UserCredentialsMatcher();
  }

  @Bean
  public EhCacheManager ehCacheManager() {
    EhCacheManager em = new EhCacheManager();
    em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
    return em;
  }

  /**
   * 开启shiro aop注解支持. 使用代理方式;所以需要开启代码支持;
   *
   * @param securityManager
   * @return
   */
  @Bean
  public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
      DefaultWebSecurityManager securityManager) {
    AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
    advisor.setSecurityManager(securityManager);
    return advisor;
  }

  @Bean(name = "sessionDao")
  public EnterpriseCacheSessionDAO sessionDao() {
    EnterpriseCacheSessionDAO sessionDao = new EnterpriseCacheSessionDAO();
    sessionDao.setActiveSessionsCacheName("activeSessionCache");
    sessionDao.setCacheManager(ehCacheManager());
    return sessionDao;
  }

}
